<?php
/**
 * 左右值树型数据结构
 * behavior配置如下
 * <pre>
 * ...
 * public function behaviors() {
 *  return array(
 *   'tree' => array(
 *    'class' => 'lib.behaviors.ar.tree.CLRTreeBehavior'
 *    // 最多可以添加3级
 *    'maxLayer' => '3',
 *   ),
 *  )
 * }
 * ...
 * </pre>
 * 表中必须存在的字段如下：
 * <ul>
 * 	<li>id 自增长主键</li>
 * 	<li>parent_id 父节点ID</li>
 * 	<li>left_num</li>
 * 	<li>right_num</li>
 * 	<li>layer 当前节点层级</li>
 * </ul>
 * @author yangdongqi <yangdongqi@hayzone.com>
 * @version 1.0 2011-12-08
 * @package lib.behaviors.ar.tree
 */
Yii::import('lib.behaviors.ar.tree.CTreeBehavior');
class CLRTreeBehavior extends CTreeBehavior {
	
	/**
	 * owner的旧attributes
	 * @var array
	 */
	protected $_oldAttributes = array();
	
	public function messages() {
		return array_merge(parent::messages(), array(
			'moveError' => '不能将自身移动到自身节点或其的子节点下.',
		));
	}
	
	public function validators() {
		return array_merge(parent::validators(), array(
			'cantMove',
		));
	}
	
	/**
	 * 不能移动节点
	 * @return boolean
	 */
	public function cantMove() {
		$model = $this->owner;
		$nodes = $this->getDescendants();
		$ids = CHtml::listData($nodes, 'id', 'id');
		$ids[$model->id] = $model->id;
		$parent = $this->getParent();
		// 无法移动
		if(isset($ids[$parent->id])) {
			$model->addError('parent_id', $this->tMsg('moveError'));
			return false;
		}
		return true;
	}
	
	/**
	 * 保存旧记录
	 * @see CActiveRecordBehavior::afterFind()
	 * @return void
	 */
	public function afterFind($event) {
		$this->_oldAttributes = $this->owner->attributes;
	}
	
	/**
	 * 验证
	 * @see CActiveRecordBehavior::beforeSave()
	 * @return void
	 */
	public function beforeValidate($event) {
		$model = $this->owner;
		$parent = $this->parent;
		
		if($model->isNewRecord) {
			$model->left_num = $parent->left_num + 1;
			$model->right_num = $parent->left_num + 2;
			$model->layer = $parent->layer + 1;
		}
		
		parent::beforeValidate($event);
		
	}
	
	/**
	 * 物理创建节点或移动节点
	 * @see CActiveRecordBehavior::afterSave()
	 * @return void
	 */
	public function afterSave($event) {
		$model = $this->owner;
		if($model->isNewRecord) {
			// 新增物理创建
			$parent = $this->getParent();
			$dao = Yii::app()->db;
			$updateSql = "
				UPDATE {$this->tableRawName} 
				SET `left_num` = `left_num` + 2 
				WHERE `left_num` > '{$parent->left_num}' AND `id` <> {$model->id}
			";
			$dao->createCommand($updateSql)->execute();
			
			$updateSql = "
				UPDATE {$this->tableRawName} 
				SET `right_num` = `right_num` + 2 
				WHERE `right_num` > '{$parent->left_num}' AND `id` <> {$model->id}
			";
			$dao->createCommand($updateSql)->execute();
		} elseif($model->parent_id != $this->_oldAttributes['parent_id']) {
			// 节点移动
			$this->moveNode();
		}
	}
	
	/**
	 * 获取后代节点
	 * @param int $deep 深度
	 * @return array
	 */
	public function getDescendants($deep = 999) {
		$criteria = new CDbCriteria();
		$parentNode = $this->owner;
		$criteria->condition = "`layer` <= :layer AND left_num > :l AND right_num < :r";
		$criteria->params = array(
			':layer' => $parentNode->layer + $deep,
			':l' => $parentNode->left_num,
			':r' => $parentNode->right_num,
		);
		
		return $this->owner->findAll($criteria);
	}
	
	
	
	/**
	 * 获取祖先节点
	 * @param boolean $hasSelf 是否包含自身节点
	 * @param int $deep 深度
	 * @return array
	 */
	public function getAncestors($hasSelf = true, $deep = 999) {
		$criteria = new CDbCriteria();
		$node = $this->owner;
		$leftNum = $hasSelf ? $node->left_num + 1 : $node->left_num;
		$rightNUm = $hasSelf ? $node->right_num -1 : $node->right_num;
		$criteria->condition = "
			`left_num` < :l
			AND `right_num` > :r
			AND `layer` <> 0
		";
		$criteria->params = array(
			':l' => $leftNum,
			':r' => $rightNUm,
		);
		$criteria->order = "layer ASC";
		return $this->owner->findAll($criteria);
	}
	
	
	/**
	 * 获取子节点数
	 * @return int
	 */
	public function getChildCount() {
		return ($this->owner->right_num - $this->owner->left_num - 1) / 2;
	}
	
	/**
	 * 删除节点
	 * @return void
	 */
	public function afterDelete($event) {
		$node = $this->owner;
		
		$dao = Yii::app()->db;
		
		$deleteSql = "
			DELETE FROM {$this->tableRawName}
			WHERE `left_num` BETWEEN '{$node->left_num}'
			AND '{$node->right_num}';
		";
		
		$dao->createCommand($deleteSql)->execute();
		
		$updateSql = "
			UPDATE {$this->tableRawName}
			SET `left_num` = `left_num` - ({$node->right_num} - {$node->left_num} + 1)
			WHERE `left_num` > '{$node->left_num}'
		";
		$dao->createCommand($updateSql)->execute();
		
		$updateSql = "
			UPDATE {$this->tableRawName}
			SET right_num = right_num - ({$node->right_num} - {$node->left_num} + 1)
			WHERE right_num > '{$node->right_num}'
		";
		$dao->createCommand($updateSql)->execute();
	}
	
	
	/**
	 * 移动节点
	 * @return boolean
	 */
	protected function moveNode() {
		$db = Yii::app()->db;
		$SelfCatagory = $this->owner;
		$NewCatagory = $this->owner->findByPk($this->owner->parent_id);
		
		$selfLayer = $this->_oldAttributes['layer']; 
		$newLayer = $NewCatagory->layer+1;
		$diffLayer = $newLayer - $selfLayer; #取layer差集
		
		$SelfLft = $SelfCatagory->left_num;
		$SelfRgt = $SelfCatagory->right_num;
		$Value = $SelfRgt-$SelfLft;
		
		//取得所有分类的ID方便更新左右值
		$CatagoryIDS=$this->getDescendants();
		$IDS[] = $SelfCatagory->primaryKey;
		foreach($CatagoryIDS as $v){
		    $IDS[]=$v->primaryKey;
		}
		$InIDS=implode(",",$IDS);
		
		$ParentLft=$NewCatagory->left_num;
		$ParentRgt=$NewCatagory->right_num;
		
		if($ParentRgt>$SelfRgt){
		    $UpdateLeftSQL="
		    	UPDATE {$this->tableRawName} 
		    	SET `left_num` = `left_num` - {$Value} - 1 
		    	WHERE `left_num` > {$SelfRgt} 
		    	AND `right_num` <= {$ParentRgt}
		    ";
		    $UpdateRightSQL = "
		    	UPDATE {$this->tableRawName} 
		    	SET `right_num` = `right_num` - {$Value} - 1 
		    	WHERE `right_num` > $SelfRgt 
		    	AND `right_num` < $ParentRgt
		    ";
		    $TmpValue = $ParentRgt - $SelfRgt - 1;
		    $UpdateSelfSQL = "
		    	UPDATE {$this->tableRawName} 
		    	SET `left_num` = `left_num` + $TmpValue, `right_num` = `right_num` + $TmpValue 
		    	WHERE `{$this->owner->tableSchema->primaryKey}` 
		    	IN($InIDS)
		    ";
		    $updateLayerSQL = "
		    	UPDATE {$this->tableRawName} 
		    	SET `layer` = `layer` + $diffLayer 
		    	WHERE `{$this->owner->tableSchema->primaryKey}` 
		    	IN($InIDS)
		    ";
		 } else {
		    $UpdateLeftSQL = "
		    	UPDATE {$this->tableRawName} 
		    	SET `left_num` = `left_num` + $Value + 1 
		    	WHERE `left_num` > $ParentRgt 
		    	AND `left_num` < $SelfLft
		    ";
		    $UpdateRightSQL="
		    	UPDATE {$this->tableRawName} 
		    	SET `right_num`=`right_num`+$Value+1 
		    	WHERE `right_num`>=$ParentRgt 
		    	AND `right_num`<$SelfLft";
		    $TmpValue=$SelfLft-$ParentRgt;
		    $UpdateSelfSQL="
		    	UPDATE {$this->tableRawName} 
		    	SET `left_num`=`left_num`-$TmpValue,`right_num`=`right_num`-$TmpValue 
		    	WHERE `{$this->owner->tableSchema->primaryKey}` 
		    	IN($InIDS)
		    ";
			$updateLayerSQL = "
				UPDATE {$this->tableRawName} 
				SET `layer`=`layer`+$diffLayer 
				WHERE `{$this->owner->tableSchema->primaryKey}` 
				IN($InIDS)
			";
		}
		$db->createCommand($UpdateLeftSQL)->execute();
		$db->createCommand($UpdateRightSQL)->execute();
		$db->createCommand($UpdateSelfSQL)->execute();
		$db->createCommand($updateLayerSQL)->execute();
		
		return true;
	}
	
	/**
	 * 表名
	 * @return string
	 */
	protected function getTableRawName() {
		return $this->owner->tableSchema->rawName;
	}
	
	
}

?>